
#region Imports

using System;
using System.Collections.Generic;
using System.Text;

using Justa.Data.Reflection;

#endregion

namespace Justa.Data.Entity
{
    /// <summary>
    /// 
    /// </summary>
    /// 
    public sealed class QueryBuilder
    {
        /// <summary>
        /// 
        /// </summary>
        /// 
        public static readonly string Space = @" ";

        /// <summary>
        /// 
        /// </summary>
        /// 
        public static readonly string Open = @"( ";

        /// <summary>
        /// 
        /// </summary>
        /// 
        public static readonly string Close = @") ";

        /// <summary>
        /// 
        /// </summary>
        public static readonly string ColumnSeperator = @", ";

        /// <summary>
        /// 
        /// </summary>
        /// 
        public static readonly string BeginColumnValueSeperator = @"'";

        /// <summary>
        /// 
        /// </summary>
        /// 
        public static readonly string EndColumnValueSeperator = @"', ";

        /// <summary>
        /// 
        /// </summary>
        /// 
        internal static readonly string SelectAllStatement = "select * from {0} ";

        /// <summary>
        /// 
        /// </summary>
        /// 
        internal static readonly string SelectStatement = "select * from {0} where {1} = {2} ";

        /// <summary>
        /// 
        /// </summary>
        /// 
        internal static readonly string DeleteStatement = "delete from {0} where {1} = {2} ";

        /// <summary>
        /// 
        /// </summary>
        /// 
        internal static readonly string InsertStatement = "insert into ";

        /// <summary>
        /// 
        /// </summary>
        /// 
        internal static readonly string Values = " values ";

        /// <summary>
        /// Prevent direct instantiation.
        /// </summary>
        /// 
        private QueryBuilder()
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="definition"></param>
        /// <param name="pk"></param>
        /// 
        /// <returns></returns>
        /// 
        public static string GetSelectQuery(EntityDefinition definition, Object pk)
        {
            string query = null;

            try
            {
                query = string.Format(SelectStatement, definition.EntityAttribute.Name, definition.IdAttribute.Name, pk);
                return query;
            }
            catch (Exception exception)
            {
                throw new QueryBuilderException("Failed to construct select statement.", exception);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="entityDefinition"></param>
        /// <param name="pk"></param>
        /// 
        /// <returns></returns>
        /// 
        public static string GetDeleteQuery(EntityDefinition entityDefinition, Object pk)
        {
            string query = null;

            try
            {
                query = string.Format(DeleteStatement, entityDefinition.EntityAttribute.Name, entityDefinition.IdAttribute.Name, pk);
                return query;
            }
            catch (Exception exception)
            {
                throw new QueryBuilderException("Failed to construct delete statement.", exception);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="definition"></param>
        /// <param name="entity"></param>
        /// 
        /// <returns></returns>
        /// 
        public static string GetInsertQuery(EntityDefinition definition, Object entity)
        {
            try
            {
                StringBuilder sb = new StringBuilder(InsertStatement);
                sb.Append(definition.EntityAttribute.Name);
                sb.Append(QueryBuilder.Space);
                sb.Append(QueryBuilder.Open);

                /**
                 * construct column list (col, col1, col2, etc...)
                 */
                foreach (KeyValuePair<string, ColumnType> columnType in definition.ColumnTypes)
                {
                    sb.Append(QueryBuilder.Space);
                    sb.Append(columnType.Value.Column);
                    sb.Append(QueryBuilder.ColumnSeperator);
                }

                // remove trailing column seperator on last column
                sb = sb.Remove((sb.Length - 2), 1);
                sb.Append(QueryBuilder.Close);
                sb.Append(Values);
                sb.Append(QueryBuilder.Open);

                /**
                 * construct column value list. ('value', 'value1', 'value2', etc...)
                 */
                foreach (KeyValuePair<string, ColumnType> columnType in definition.ColumnTypes)
                {
                    /**
                     * Process association by getting join column value
                     */
                    if (columnType.Value.ColumnTypeEnum.Equals(ColumnTypeEnum.JOIN))
                    {
                        sb.Append(EntityReflector.GetValueOfJoinColumn(entity));
                        sb.Append(QueryBuilder.ColumnSeperator);
                        continue;
                    }

                    if (columnType.Value.ColumnTypeEnum.Equals(ColumnTypeEnum.ID))
                    {
                        /**
                         * Check if the current PK/ID column should be 
                         * auto generated
                         */
                        if (definition.IdAttribute.IsGenerated)
                        {
                            /**
                             * todo: need to remove dependency on EntityManager 
                             * this makes the relationship between EntityMnager and
                             * QueryBuilder bi-directional.
                             */
                            //EntityManager manager = new EntityManager("IBEntityManager");
                            //long pk = manager.GeneratePrimaryKey(columnType.Value.Column);
                            //sb.Append(pk);
                        }
                        else
                        {
                            /**
                             * Here we assume the specified PK/ID value on the given entity
                             * is in sync with the database (no need to auto-generate).
                             */
                            Object pk = definition.EntityType.GetField(columnType.Value.Property).GetValue(entity);
                            sb.Append(pk);
                        }
                        sb.Append(QueryBuilder.ColumnSeperator);
                    }
                    else
                    {
                        /**
                         * Column value here is a plain old column, no join, pk etc...
                         */
                        sb.Append(QueryBuilder.BeginColumnValueSeperator);
                        sb.Append(definition.EntityType.GetField(columnType.Value.Property).GetValue(entity));
                        sb.Append(QueryBuilder.EndColumnValueSeperator);
                    }
                }

                // remove trailing column value seperator on last value
                sb = sb.Remove((sb.Length - 2), 1);
                sb.Append(QueryBuilder.Close);

                return sb.ToString();
            }
            catch (Exception exception)
            {
                throw new QueryBuilderException("Failed to construct insert statement.", exception);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <param name="definition"></param>
        /// <param name="property"></param>
        /// <param name="change"></param>
        /// <param name="pk"></param>
        /// 
        /// <returns></returns>
        /// 
        public static string GetUpdateQuery(EntityDefinition definition, string property, Object change, object pk)
        {
            return string.Format(@"update {0} set {1} = {2} where {3} = {4}", definition.EntityAttribute.Name, property, change, definition.IdAttribute.Name, pk);
        }

        /// <summary>
        /// 
        /// </summary>
        /// 
        /// <returns></returns>
        /// 
        public static string GetInnerJoinQuery()
        {
            string query = @"SELECT Customer.customer_id, Customer.customer_name, Toyota.toyota_id, Toyota.toyota_model FROM Toyota INNER JOIN Customer ON Toyota.customer_id = Customer.customer_id";
            query = @"SELECT Toyota.*, Customer.* FROM Toyota INNER JOIN Customer ON Toyota.customer_id = Customer.customer_id";
            query = @"SELECT {Toyota}.*, {Customer}.* FROM {Toyota} INNER JOIN {Customer} ON {Toyota}.{customer_id} = {Customer}.{customer_id}";

            return query;
        }
    }
}
